package cn.edu.dgut.css.sai.course.wechatoauth2demo.config;

import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;

import javax.servlet.http.HttpServletRequest;
import java.util.function.Consumer;

/**
 * 定制 授权请求解释器 {@link OAuth2AuthorizationRequestResolver}
 * <br><br>
 * OAuth2 {@link ClientRegistration}最终属性如何构造，看{@link OAuth2ClientPropertiesRegistrationAdapter}。
 * <p>
 * 1、先从自定义的或内置的 Provider 复制 下面的属性 到 {@link ClientRegistration.Builder}
 * private static Builder getBuilder(Builder builder, Provider provider) {
 * PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
 * map.from(provider::getAuthorizationUri).to(builder::authorizationUri);
 * map.from(provider::getTokenUri).to(builder::tokenUri);
 * map.from(provider::getUserInfoUri).to(builder::userInfoUri);
 * map.from(provider::getUserInfoAuthenticationMethod).as(AuthenticationMethod::new)
 * .to(builder::userInfoAuthenticationMethod);
 * map.from(provider::getJwkSetUri).to(builder::jwkSetUri);
 * map.from(provider::getUserNameAttribute).to(builder::userNameAttributeName);
 * return builder;
 * }
 * <p>
 * 2、再 从 属性文件 自定义的 registration 复制下面的属性 到 {@link ClientRegistration.Builder}
 * <p>
 * PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
 * map.from(properties::getClientId).to(builder::clientId);
 * map.from(properties::getClientSecret).to(builder::clientSecret);
 * map.from(properties::getClientAuthenticationMethod).as(ClientAuthenticationMethod::new)
 * .to(builder::clientAuthenticationMethod);
 * map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new)
 * .to(builder::authorizationGrantType);
 * map.from(properties::getRedirectUri).to(builder::redirectUriTemplate);
 * map.from(properties::getScope).as(StringUtils::toStringArray).to(builder::scope);
 * map.from(properties::getClientName).to(builder::clientName);
 * return builder.build();
 * <p>
 * 3、最终调用 {@link ClientRegistration.Builder#build()} 方法构造最终的 {@link ClientRegistration} , Provider 与 registration 的标签需要一致。
 * <br>
 * 4、再看这个方法{@code DefaultOAuth2AuthorizationRequestResolver#expandRedirectUri}，是如何生成最终的回调地址的。利用了redirectUriTemplate属性。你可以设置一个完全的地址，也可以利用变量让它自动扩展而成。
 * <br><br>
 *
 * @author sai
 * @code Consumer<OAuth2AuthorizationRequest.Builder>
 * @see OAuth2AuthorizationRequestResolver
 * @see DefaultOAuth2AuthorizationRequestResolver
 * @see OAuth2AuthorizationRequest.Builder
 * @see OAuth2AuthorizationRequest.Builder#build()
 * @see OAuth2AuthorizationRequest#from(OAuth2AuthorizationRequest)
 * @see OAuth2AuthorizationRequestRedirectFilter
 * @see OAuth2ClientPropertiesRegistrationAdapter
 * @see ClientRegistration.Builder
 * @since 2020/12/26
 */
public final class WeChatAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {

    private final DefaultOAuth2AuthorizationRequestResolver defaultOAuth2AuthorizationRequestResolver;
    private final WeChatOAuth2ClientProperties weChatOAuth2ClientProperties;

    public WeChatAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, WeChatOAuth2ClientProperties weChatOAuth2ClientProperties) {
        this.weChatOAuth2ClientProperties = weChatOAuth2ClientProperties;
        defaultOAuth2AuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
        defaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(this::AuthorizationRequestCustomizer);
    }

    /**
     * <p>根据{@link DefaultOAuth2AuthorizationRequestResolver}的源码分析，在{@link OAuth2AuthorizationRequest.Builder} 执行build()之前，会调用一个{@link Consumer}配置Builder，算是一个扩展点，让我们可以修改Builder。</p>
     * <p>我们可以利用这个扩展点，再结合Builder的{@link OAuth2AuthorizationRequest.Builder#parameters(Consumer)}，修改最终生成授权请求的uri的查询参数。</p>
     * <p>另外，{@link OAuth2AuthorizationRequest.Builder}本身有一个对最终的uriBuilder有一个扩展点，可以修改uri。</p>
     * <p>本例子结合上面两个扩展点，修改授权请求的uri。</p>
     * <p>关于URI中的fragment的说明：https://www.jianshu.com/p/2c07fbb52b45</p>
     *
     * @see OAuth2AuthorizationRequest.Builder
     * @see OAuth2ParameterNames
     */
    private void AuthorizationRequestCustomizer(OAuth2AuthorizationRequest.Builder builder) {

        builder.parameters(uriQueryParamMap -> {
            // 增加一个appid的key，再删除原来client-id的key。
            uriQueryParamMap.put("appid", weChatOAuth2ClientProperties.getAppId());
            uriQueryParamMap.remove(OAuth2ParameterNames.CLIENT_ID);
        });

        builder.authorizationRequestUri(uriBuilder -> {
            // 在uri中添加一个fragment。
            uriBuilder.fragment("wechat_redirect");
            return uriBuilder.build();
        });
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        return defaultOAuth2AuthorizationRequestResolver.resolve(request);
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
        return defaultOAuth2AuthorizationRequestResolver.resolve(request, clientRegistrationId);
    }

}
